/* * This file is part of the Haven & Hearth game client. * Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and * Björn Johannessen <johannessen.bjorn@gmail.com> * * Redistribution and/or modification of this file is subject to the * terms of the GNU Lesser General Public License, version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Other parts of this source tree adhere to other copying * rights. Please see the file `COPYING' in the root directory of the * source tree for details. * * A copy the GNU Lesser General Public License is distributed along * with the source tree of which this file is a part in the file * `doc/LPGL-3'. If it is missing for any reason, please see the Free * Software Foundation's website at <http://www.fsf.org/>, or write * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package haven; import java.io.InputStream; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; public class Audio { public static boolean enabled = true; private static Thread player; public static final AudioFormat fmt = new AudioFormat(44100, 16, 2, true, false); private static Collection<CS> ncl = new LinkedList<CS>(); private static Object queuemon = new Object(); private static Collection<Runnable> queue = new LinkedList<Runnable>(); private static int bufsize = 32768; public interface CS { public boolean get(double[] sample); } public static class DataClip implements CS { private InputStream clip; private double vol, sp; private int ack = 0; private double[] ov = new double[2]; public boolean eof; public DataClip(InputStream clip, double vol, double sp) { this.clip = clip; this.vol = vol; this.sp = sp; } public DataClip(InputStream clip) { this(clip, 1.0, 1.0); } public void finwait() throws InterruptedException { synchronized(this) { if(eof) return; wait(); } } public boolean get(double[] sm) { try { ack += 44100.0 * sp; while(ack >= 44100) { for(int i = 0; i < 2; i++) { int b1 = clip.read(); int b2 = clip.read(); if((b1 < 0) || (b2 < 0)) { synchronized(this) { eof = true; notifyAll(); } return(false); } int v = b1 + (b2 << 8); if(v >= 32768) v -= 65536; ov[i] = ((double)v / 32768.0) * vol; } ack -= 44100; } } catch(java.io.IOException e) { synchronized(this) { eof = true; notifyAll(); } return(false); } for(int i = 0; i < 2; i++) sm[i] = ov[i]; return(true); } } private static class Player extends HackThread { private Collection<CS> clips = new LinkedList<CS>(); private int srate, nch = 2; Player() { super("Haven audio player"); setDaemon(true); srate = (int)fmt.getSampleRate(); } private void fillbuf(byte[] buf, int off, int len) { double[] val = new double[nch]; double[] sm = new double[nch]; while(len > 0) { for(int i = 0; i < nch; i++) val[i] = 0; for(Iterator<CS> i = clips.iterator(); i.hasNext();) { CS cs = i.next(); if(!cs.get(sm)) { i.remove(); continue; } for(int ch = 0; ch < nch; ch++) val[ch] += sm[ch]; } for(int i = 0; i < nch; i++) { int iv = (int)(val[i] * 32767.0); if(iv < 0) { if(iv < -32768) iv = -32768; iv += 65536; } else { if(iv > 32767) iv = 32767; } buf[off++] = (byte)(iv & 0xff); buf[off++] = (byte)((iv & 0xff00) >> 8); len -= 2; } } } public void run() { SourceDataLine line = null; try { try { line = (SourceDataLine)AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, fmt)); line.open(fmt, bufsize); line.start(); } catch(Exception e) { e.printStackTrace(); return; } byte[] buf = new byte[1024]; while(true) { if(Thread.interrupted()) throw(new InterruptedException()); synchronized(queuemon) { Collection<Runnable> queue = Audio.queue; Audio.queue = new LinkedList<Runnable>(); for(Runnable r : queue) r.run(); } synchronized(ncl) { for(CS cs : ncl) clips.add(cs); ncl.clear(); } fillbuf(buf, 0, 1024); for(int off = 0; off < buf.length; off += line.write(buf, off, buf.length - off)); } } catch(InterruptedException e) { } finally { synchronized(Audio.class) { player = null; } if(line != null) line.close(); } } } private static synchronized void ckpl() { if(enabled) { if(player == null) { player = new Player(); player.start(); } } else { ncl.clear(); } } public static void play(CS clip) { synchronized(ncl) { ncl.add(clip); } ckpl(); } public static void play(final InputStream clip, final double vol, final double sp) { play(new DataClip(clip, vol, sp)); } public static void play(byte[] clip, double vol, double sp) { play(new DataClip(new java.io.ByteArrayInputStream(clip), vol, sp)); } public static void play(byte[] clip) { play(clip, Config.getSFXVolume(), 1.0); } public static void queue(Runnable d) { synchronized(queuemon) { queue.add(d); } ckpl(); } private static void playres(Resource res) { Collection<Resource.Audio> clips = res.layers(Resource.audio); int s = (int)(Math.random() * clips.size()); Resource.Audio clip = null; for(Resource.Audio cp : clips) { clip = cp; if(--s < 0) break; } play(clip.clip); } public static void play(final Resource clip) { queue(new Runnable() { public void run() { if(clip.loading) queue.add(this); else playres(clip); } }); } public static void play(final Indir<Resource> clip) { queue(new Runnable() { public void run() { Resource r = clip.get(); if(r == null) queue.add(this); else playres(r); } }); } public static byte[] readclip(InputStream in) throws java.io.IOException { AudioInputStream cs; try { cs = AudioSystem.getAudioInputStream(fmt, AudioSystem.getAudioInputStream(in)); } catch(UnsupportedAudioFileException e) { throw(new java.io.IOException("Unsupported audio encoding")); } java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream(); byte[] bbuf = new byte[65536]; while(true) { int rv = cs.read(bbuf); if(rv < 0) break; buf.write(bbuf, 0, rv); } return(buf.toByteArray()); } public static void main(String[] args) throws Exception { Collection<DataClip> clips = new LinkedList<DataClip>(); for(int i = 0; i < args.length; i++) { if(args[i].equals("-b")) { bufsize = Integer.parseInt(args[++i]); } else { DataClip c = new DataClip(new java.io.FileInputStream(args[i])); clips.add(c); } } for(DataClip c : clips) play(c); for(DataClip c : clips) c.finwait(); } static { Console.setscmd("sfx", new Console.Command() { public void run(Console cons, String[] args) { play(Resource.load(args[1])); } }); } }